package jcu.salt.client;

import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;


import jcu.sal.common.cml.CMLConstants;
import jcu.salt.Configuration;
import jcu.salt.Logger;
import jcu.salt.io.Transmittable;
import edu.sdsc.grid.io.srb.SRBAccount;
import edu.sdsc.grid.io.srb.SRBFile;
import edu.sdsc.grid.io.srb.SRBFileOutputStream;
import edu.sdsc.grid.io.srb.SRBFileSystem;
import edu.sdsc.grid.io.srb.SRBRandomAccessFile;

/**
 * This class is used to handle the connection between data turbine
 * and SRB. In SRB, a file structure like:<br>
 * ./{certain_year}/{sal_id-sensor_id}.csv<br>
 * should be handled.<br><br>
 * Furthermore, the data inside the files should be formatted as:<br>
 * {timestamp}, {value} 
 * 
 * @author SAL-T Team
 * 
 * TODO there is still some abnormal creation of directories ?!?
 */
public class SRBConnection {
	
	SRBAccount userAccount;

	private String srbUsername;
	private String srbDomain;
	private String srbPassword;
	private String srbHost;
	private String srbResource;
	private String srbHome;

	private SRBFileSystem srbFileSystem;

	private SRBFile srbWorkingDirectory;
	
	public SRBConnection() {
		initConnectionProperties();
	}
	
	/**
	 * reads out Configuration to make use of srb connection properties
	 */
	void initConnectionProperties() {
		Configuration config = Configuration.getInstance();		
		srbUsername = config.get(Configuration.SRB_USERNAME);
		srbDomain = config.get(Configuration.SRB_DOMAIN);
		srbPassword = config.get(Configuration.SRB_PASSWORD);
		srbHost = config.get(Configuration.SRB_HOST);
		srbResource = config.get(Configuration.SRB_RESOURCE);			
	}

	/**
	 * Writes the payload data from a given Transmittable into SRB.
	 * @param received the Transmittable object with the payload data
	 * @throws NullPointerException
	 * @throws IOException
	 * @throws DataTypeNotRecognizedException 
	 */
	public synchronized void writeData(Transmittable received) throws NullPointerException, IOException, DataTypeNotRecognizedException {

		// the data to transfer
		byte[] data = null;
		
		// the filename of the file to create
		String fileName = null;
		
		// Set up the data to write and write it away
		if ( received.getDataType().equals(CMLConstants.RET_TYPE_STRING) ) {
			// TODO: If a comma is inside the given String, would that be a problem for the .csv file depending on who is using the output data after it was saved?
			
			// Specify the data to be written
			data = (received.getTimestamp() + "," + new String(received.getData()) + "\n").getBytes();
			
			// Set up the file name corresponding to sal id and sensor id
			fileName = received.getSalID() + "_" + received.getSensorID() + ".csv";
			
			// Connect to SRB, go into working directory
			setUpWriteEnvironment();
			
			// write the string
			writeString(data, fileName);
			
		} else if ( received.getDataType().equals(CMLConstants.RET_TYPE_BYTE_ARRAY) ) {
			
			// Specify the data to be written
			data = received.getData();
			
			// Set up the file name corresponding to sal id and sensor id
			fileName = received.getTimestamp() + ".jpg";
			
			// Set up extra directory name
			String directoryName = new String( received.getSalID() + "_" + received.getSensorID() );
			
			// Connect to SRB, go into working directory
			setUpWriteEnvironment();
			
			// write the data
			writeByteArray(data, directoryName, fileName);
			
		} else {
			throw new DataTypeNotRecognizedException();
		}
		
		
		// Close the file system handler again
		tearDownWriteEnvironment();		
	}

	
	/**
	 * Used to write a byte array of data into a file.<br>
	 * The byte array could for example be representing data of 
	 * a picture file, so that the data is directly written into 
	 * a foo.jpg file. 
	 * @param data the data to store into the file
	 * @param directoryName the subdirectory to use
	 * @param fileName the filename
	 * @throws IOException
	 */
	private void writeByteArray(byte[] data, String directoryName, String fileName) throws IOException {

		// Change into the directory one structure further down
		//  e.g. home/{username@domain}/{date}/{salid_sensorid}/
		
		srbWorkingDirectory = new SRBFile(srbWorkingDirectory, directoryName);
		
		// Define the remote file
		SRBFile writeFile = new SRBFile(srbWorkingDirectory, fileName);

		// Create a new file if the file does not exist yet
		if (!writeFile.exists()) {
			if (!writeFile.createNewFile()) {
				System.err.println("Create new file '"+fileName+"' failed");
			} else {
				Logger.log("Created new file '" + directoryName + "/" + fileName + "'");
				SRBFileOutputStream os = new SRBFileOutputStream(writeFile);
				os.write(data);
				// Debug check if conversion went ok
				Logger.log("Data written to SRB (" + data.length + " bytes)"); 
				os.close();
			}
		}
		// Append the data if file already exists
		else {
			Logger.log("Using existing '" + fileName + "'.");
			SRBRandomAccessFile raf = new SRBRandomAccessFile(writeFile, "rw");
			raf.seek(raf.length()); // FIXME is this right? does it append?
			raf.write( data );
			Logger.log("Data written to SRB (" + data.length + " bytes)"); 
			raf.close();
		}
		
	}

	private void writeString(byte[] data, String fileName) throws IOException {		
		
		// Define the remote file
		SRBFile writeFile = new SRBFile(srbWorkingDirectory, fileName);

		// Create a new file if the file does not exist yet
		if (!writeFile.exists()) {
			if (!writeFile.createNewFile()) {
				System.err.println("Create new file '"+fileName+"' failed");
			} else {
				Logger.log("Created new file '" + fileName + "'");
				SRBFileOutputStream os = new SRBFileOutputStream(writeFile);
				os.write(data);
				// Debug check if conversion went ok
				Logger.log("Data written to SRB: " + new String( data ) ); 
				os.close();
			}
		}
		// Append the data if file already exists
		else {
			Logger.log("Using existing file '" + fileName + "'.");
			SRBRandomAccessFile raf = new SRBRandomAccessFile(writeFile, "rw");
			raf.seek(raf.length()); // FIXME is this right? does it append?
			raf.write( data );
			Logger.log("Data written to SRB: " + new String( data ) ); 
			raf.close();
		}
		
	}

	private void setUpWriteEnvironment() throws NullPointerException, IOException {

		// Set up home directory path		
		srbHome = "/home/" + srbUsername + "." + srbDomain;
		
		// Set user account		
		userAccount = new SRBAccount(srbHost, 5544, srbUsername, srbPassword,
				srbHome, srbDomain, srbResource);

		// Get current date in UTC time zone		
		Calendar local = new GregorianCalendar();
		Calendar utcCal = new GregorianCalendar(TimeZone.getTimeZone("Universal"));
		utcCal.setTimeInMillis(local.getTimeInMillis());
		
		// Set up the directory name corresponding to the date		
		String dirName = utcCal.get(Calendar.YEAR) + "-" + (utcCal.get(Calendar.MONTH)+1) + "-" + utcCal.get(Calendar.DAY_OF_MONTH);
		
		// Connect to file system		
		srbFileSystem = new SRBFileSystem(userAccount);
		
		//  Specify remote directory		
		srbWorkingDirectory = new SRBFile(srbFileSystem, srbHome);
		if (!srbWorkingDirectory.exists()) {
			System.err.println("Home doesn't exist");
		}
		
		// Create a new directory in the working directory if it did not exist already		
		if (!new SRBFile(srbWorkingDirectory, dirName).exists()) {
			if (!new SRBFile(srbWorkingDirectory, dirName).mkdir()) {
				System.err.println("Creation of '" + dirName + "' failed");
			} else {
				Logger.log("'" + dirName + "' created.");
			}
		} else {
			Logger.log("Using existing directory '" + dirName + "'.");
		}
		
		// Change into the directory
		srbWorkingDirectory = new SRBFile(srbWorkingDirectory, dirName);
	}
	

	private void tearDownWriteEnvironment() throws IOException {		
		srbFileSystem.close();
		Logger.log("Turbine Client went through!");		
	}

}
